package edu.xhu.oss.utils;

import com.aliyun.oss.*;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import edu.xhu.oss.config.BiliOssConfigProperties;
import com.aliyun.oss.common.auth.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.*;

import lombok.NonNull;

import java.net.URLDecoder;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@RequiredArgsConstructor
@Component
@EnableConfigurationProperties({BiliOssConfigProperties.class})
public class BiliOssUtils {


    //已经内置好分片桶，正式桶
    private final BiliOssConfigProperties biliOssConfigProperties;


    /**
     * @param objectName 图片名
     * @param file       图片文件流
     * @return 图片的URL，如果为空，说明上传失败
     */
    public String uploadImageFile(@NonNull String objectName, @NonNull MultipartFile file) {
        //图片的URL
        String imageUrl = null;
        //桶路径
        String bucketName = biliOssConfigProperties.getImageBucket();
        String fileName = getFileName(objectName) + "jpg";
        imageUrl = biliOssConfigProperties.getImageBucketUrl() + fileName;
        //建立连接
        OSS ossClient = new OSSClient(biliOssConfigProperties.getEndpoints(), biliOssConfigProperties.getAccessKey(), biliOssConfigProperties.getSecretKey());
        if (ossClient != null) {
            try {
                //上传图片
                ossClient.putObject(bucketName, fileName, file.getInputStream());
                return imageUrl;
            } catch (IOException e) {
                log.error("图片上传失败!|参数：bucketName:{},originalFilename:{}|异常:{}", bucketName, objectName, e);
                return imageUrl;
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
        return imageUrl;
    }

    public String uploadImageFile(@NonNull String objectName, @NonNull String filePath) {
        //图片的URL
        String imageUrl = null;
        //桶路径
        String bucketName = biliOssConfigProperties.getImageBucket();
        String fileName = getFileName(objectName);
        imageUrl = biliOssConfigProperties.getImageBucketUrl() + fileName;
        //建立连接
        OSS ossClient = new OSSClient(biliOssConfigProperties.getEndpoints(), biliOssConfigProperties.getAccessKey(), biliOssConfigProperties.getSecretKey());
        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, new File(filePath));

            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return imageUrl;
    }

    /**
     * 分片视频上传
     * @param objectName
     * @param filePath
     * @return
     */
    public String uploadVideoFile(@NonNull String objectName, @NonNull String filePath) {
        String bucketName=biliOssConfigProperties.getVideoBucket();
        String finalFileName = getFileName(objectName);
        // 创建OSSClient实例。
        OSS ossClient = new OSSClient(biliOssConfigProperties.getEndpoints(), biliOssConfigProperties.getAccessKey(), biliOssConfigProperties.getSecretKey());
        try {
            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, finalFileName);


            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId。
            String uploadId = upresult.getUploadId();

            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags =  new ArrayList<PartETag>();
            // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
            final long partSize = 1 * 1024 * 1024L;   //1 MB。

            // 根据上传的数据大小计算分片数。以本地文件为例，说明如何通过File.length()获取上传数据的大小。
            final File sampleFile = new File(filePath);
            long fileLength = sampleFile.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(finalFileName);
                uploadPartRequest.setUploadId(uploadId);
                // 设置上传的分片流。
                // 以本地文件为例说明如何创建FIleInputstream，并通过InputStream.skip()方法跳过指定数据。
                InputStream instream = new FileInputStream(sampleFile);
                instream.skip(startPos);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
                uploadPartRequest.setPartNumber( i + 1);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, finalFileName, uploadId, partETags);
            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            System.out.println(completeMultipartUploadResult.getETag());
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return biliOssConfigProperties.getVideoBucketUrl() + finalFileName;
    }
    /**
     * 生成文件名称
     *
     * @param objectName
     * @return
     */
    private String getFileName(String objectName) {
        String ext = "." + FilenameUtils.getExtension(objectName);    // 获取文件后缀
        String uuid = System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "");
        String fileName = uuid + ext;
        return fileName;
    }


    /**
     * 文件分块上传
     *
     * @param md5        对象名称-视频的md5
     * @param chunkIndex 分片索引
     * @param file       文件
     */
    public Boolean uploadChunk(MultipartFile file, String md5, Integer chunkIndex) throws IOException {
        //分片上传oss
/*        try {
            if (chunkIndex != null) {
                objectName = objectName.concat("/").concat(Integer.toString(chunkIndex));
            }
            String chunkBucketName = biliOssConfigProperties.getChunkBucket();
            //建立连接
            OSS ossClient=new OSSClient(biliOssConfigProperties.getEndpoints(),biliOssConfigProperties.getAccessKey(),biliOssConfigProperties.getSecretKey());
            if(ossClient==null){
                log.error("Oss连接失效!|参数：objectName:{}", objectName);
                return false;
            }
            ossClient.putObject(chunkBucketName, objectName, file.getInputStream());

            log.debug("上传到oss文件|uploadFile|参数：chunkBucketName：{}，objectName：{}，chunkIndex：{}"
                    , chunkBucketName, objectName, chunkIndex);
            return true;
        } catch (Exception e) {
            log.error("文件上传到Oss异常|参数：chunkBucketName:{},objectName:{},chunkIndex:{}|异常:{}", biliOssConfigProperties.getChunkBucket(), objectName, chunkIndex, e);
            return false;
        }*/
        // 构建分片文件名
        if (chunkIndex != null) {
            md5 = md5.concat("-").concat(Integer.toString(chunkIndex));
        }
        String relativeFolderPath = "../data"; // 文件将保存在项目的上一级目录中的 data 文件夹中
        // 获取项目根路径
        String projectRootPath = new File("").getAbsolutePath();
        // 构建完整的文件保存路径
        String absoluteFolderPath = new File(projectRootPath, relativeFolderPath).getCanonicalPath();
        // 创建目标文件夹
        File folder = new File(absoluteFolderPath);
        if (!folder.exists()) {
            folder.mkdirs(); // 创建目标文件夹
        }
        // 构建文件保存路径
        String relativeFilePath = Paths.get(relativeFolderPath, md5).toString();
        String absoluteFilePath = new File(projectRootPath, relativeFilePath).getCanonicalPath();
        // 创建文件对象
        File fileToSave = new File(absoluteFilePath);
        // 检查文件是否存在，如果存在则返回 false
        if (fileToSave.exists()) {
            log.error("文件 " + absoluteFilePath + " 已存在");
            return false;
        }
        // 将文件保存到指定路径
        file.transferTo(fileToSave);
        return true;
    }

    public Integer getChunkCount(String md5) {
        // 查询本地
        // 获取分片文件的存储目录
        File chunkDir = new File(biliOssConfigProperties.getReadPath());
        // 获取存储在目录中的所有分片文件
        File[] chunkFiles = chunkDir.listFiles((dir, name) -> name.startsWith(md5 + "/"));
        // 返回还没上传的分片序号
        if (chunkFiles == null) {
            return 0;
        }
        return chunkFiles.length;
/*        OSS ossClient=new OSSClient(biliOssConfigProperties.getEndpoints(),biliOssConfigProperties.getAccessKey(),biliOssConfigProperties.getSecretKey());
        try{
            return ossClient.listObjects(biliOssConfigProperties.getChunkBucket(), md5 + "/").getObjectSummaries().size();
        } catch (OSSException e) {
            throw new RuntimeException(e);
        } catch (ClientException e) {
            throw new RuntimeException(e);
        }finally {
            if(ossClient!=null) {
                ossClient.shutdown();
            }
        }*/
    }

    /**
     * 文件分块合并，已经内置好分片桶，正式桶
     *
     * @param md5 视频的 md5 码
     * @return 返回视频URL，返回如果为空，说明合并分片失败
     */
    public String composeChunks(String md5) throws IOException {
        //视频URL
        String videoUrl = null;
        String relativeFolderPath = "../data"; // 文件将保存在项目的上一级目录中的 data 文件夹中
        // 获取项目根路径
        String projectRootPath = new File("").getAbsolutePath();
        // 构建完整的文件保存路径
        String absoluteFolderPath = new File(projectRootPath, relativeFolderPath).getCanonicalPath();
        // 获取分片文件的存储目录
        File chunkDir = new File(absoluteFolderPath);
        // 构建最终文件名
        String finalFileName = getFileName(md5) + "mp4";
        // 获取所有对应分片文件
        File[] chunkFiles = chunkDir.listFiles((dir, name) -> name.startsWith(md5 + "-"));
        OSS ossClient = new OSSClient(biliOssConfigProperties.getEndpoints(), biliOssConfigProperties.getAccessKey(), biliOssConfigProperties.getSecretKey());
        String videoBucket = biliOssConfigProperties.getVideoBucket();
        try {
            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(videoBucket, finalFileName);
            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId。
            String uploadId = upresult.getUploadId();
            // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
            // 如果您需要根据uploadId执行取消分片上传事件的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。
            // 如果您需要根据uploadId执行列举已上传分片的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后，且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
            // System.out.println(uploadId);
            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags = new ArrayList<PartETag>();
            // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
            final long partSize = 1 * 1024 * 1024L;   //1 MB。
            int partCount = getChunkCount(md5);
            for (File chunkFile : chunkFiles) {
                long curPartSize = chunkFile.length();
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(videoBucket);
                uploadPartRequest.setKey(finalFileName);
                uploadPartRequest.setUploadId(uploadId);
                // 设置上传的分片流。
                // 以本地文件为例说明如何创建FIleInputstream，并通过InputStream.skip()方法跳过指定数据。
                InputStream instream = new FileInputStream(chunkFile);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
                Integer partNumber = Integer.valueOf(chunkFile.getName().split("-")[1]) + 1;
                uploadPartRequest.setPartNumber(partNumber);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(videoBucket, finalFileName, uploadId, partETags);
            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            System.out.println(completeMultipartUploadResult.getETag());
            videoUrl = biliOssConfigProperties.getVideoBucketUrl() + finalFileName;
            return videoUrl;
        } catch (OSSException oe) {
            log.error("Error Message:" + oe.getErrorMessage());
            log.error("Error Code:" + oe.getErrorCode());
            log.error("Request ID:" + oe.getRequestId());
            log.error("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            log.error("Error Message:" + ce.getMessage());
        } catch (FileNotFoundException e) {
            log.error("Error Message:" + e.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return videoUrl;
/*        // 初始化分片
        if(ossClient==null){
            log.error("Oss连接失效!|参数：objectName:{},prefixObjectName:{}", objectName, md5);
            return videoUrl;
        }
        String chunkBucketName = biliOssConfigProperties.getChunkBucket();
        // 计算视频已经上传成功的分片数量
        try {
            // 列举文件。如果不设置keyPrefix，则列举存储空间下的所有文件。如果设置keyPrefix,即md5，则列举包含指定前缀的文件。
            List<OSSObjectSummary> objectSummaries = ossClient.listObjects(chunkBucketName, md5 + "/").getObjectSummaries();
            int chunkCount = objectSummaries.size();
            if (chunkCount < 1) {
                log.error("分片数量不足|参数：chunkBucketName:{},prefixObjectName:{}", chunkBucketName, md5);
                return videoUrl;
            }
            OSSObjectSummary s;
            List<PartETag> partETags = new ArrayList<>();
            for (int i = 0; i < chunkCount; i++) {
                s = objectSummaries.get(i);
                // 获取每个分片的ETag
                partETags.add(new PartETag(i + 1, s.getETag()));
            }
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(chunkBucketName, objectName, uploadId, partETags);
            // 完成分片上传
            ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            // 合并分片
            String bucketName = videoBucket; //正式桶
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            //拼接视频URL
            videoUrl =biliOssConfigProperties.getChunkBucketUrl()+getFileName(objectName);
            return videoUrl;

        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return videoUrl;*/
    }

    /**
     * 删除某个视频所有缓存的分片文件
     *
     * @param md5 视频的md5码
     */
    public void removeTemporaryChunks(String md5) throws IOException {
        String relativeFolderPath = "../data"; // 文件将保存在项目的上一级目录中的 data 文件夹中
        // 获取项目根路径
        String projectRootPath = new File("").getAbsolutePath();
        // 构建完整的文件保存路径
        String absoluteFolderPath = new File(projectRootPath, relativeFolderPath).getCanonicalPath();
        // 获取分片文件的存储目录
        File chunkDir = new File(absoluteFolderPath);
        // 构建最终文件名
        String finalFileName = getFileName(md5) + "mp4";
        // 获取所有对应分片文件
        File[] chunkFiles = chunkDir.listFiles((dir, name) -> name.startsWith(md5 + "-"));
        // 删除分片文件
        for (File chunkFile : chunkFiles) {
            if (chunkFile.exists()) {
                chunkFile.delete();
            }
        }
/*        OSS ossClient=new OSSClient(biliOssConfigProperties.getEndpoints(),biliOssConfigProperties.getAccessKey(),biliOssConfigProperties.getSecretKey());
        if(ossClient==null){
            log.error("Oss连接失效!|参数：prefixObjectName:{}", prefixObjectName);
        }
        String chunkBucketName = biliOssConfigProperties.getChunkBucket();
        try {
            ObjectListing objectListing = ossClient.listObjects(chunkBucketName, prefixObjectName);
            if (objectListing.getObjectSummaries().size() > 0) {
                List<String> keys = new ArrayList<String>();
                for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
                    System.out.println("key name: " + s.getKey());
                    keys.add(s.getKey());
                }
                DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(chunkBucketName).withKeys(keys).withEncodingType("url");
                DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(deleteObjectsRequest);
                List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
                try {
                    for(String obj : deletedObjects) {
                        String deleteObj =  URLDecoder.decode(obj, "UTF-8");
                        System.out.println(deleteObj);
                    }
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }*/
    }

}
